The Harmonized Landsat and Sentinel-2 (HLS) project produces seamless, harmonized surface reflectance data from the Operational Land Imager (OLI) and Multi-Spectral Instrument (MSI) aboard Landsat-8 and Sentinel-2 Earth-observing satellites, respectively. The aim is to produce seamless products with normalized parameters, which include atmospheric correction, cloud and cloud-shadow masking, geographic co-registration and common gridding, normalized bidirectional reflectance distribution function, and spectral band adjustment. This will provide global observation of the Earth’s surface every 2-3 days with 30 meter spatial resolution. One of the major applications that will benefit from HLS is agriculture assessment and monitoring, which is used as the use case for this tutorial.
NASA's Land Processes Distributed Active Archive Center (LP DAAC) archives and distributes HLS products in the LP DAAC Cumulus cloud archive as Cloud Optimized GeoTIFFs (COG). This tutorial will demonstrate how to query and subset HLS data using the NASA Common Metadata Repository (CMR) SpatioTemporal Asset Catalog (STAC) application programming interface (API). Because these data are stored as COGs, this tutorial will teach users how to load subsets of individual files into memory for just the bands you are interested in--a paradigm shift from the more common workflow where you would need to download a .zip/HDF file containing every band over the entire scene/tile. This tutorial covers how to process HLS data (quality filtering and EVI calculation), visualize, and "stack" the scenes over a region of interest into an xarray data array, calculate statistics for an EVI time series, and export as a comma-separated values (CSV) file--providing you with all of the information you need for your area of interest without having to download the source data file. The Enhanced Vegetation Index (EVI), is a vegetation index similar to NDVI that has been found to be more sensitive to ground cover below the vegetated canopy and saturates less over areas of dense green vegetation.
This tutorial was developed using an example use case for crop monitoring over a single large farm field in northern California. The goal of the project is to observe HLS-derived mean EVI over a farm field in northern California without downloading the entirety of the HLS source data.
This tutorial will show how to use the CMR-STAC API to investigate the HLS collections available in the cloud and search for and subset to the specific time period, bands (layers), and region of interest for our use case, load subsets of the desired COGs into a Jupyter Notebook directly from the cloud, quality filter and calculate EVI, stack the time series, visualize the time series, and export a CSV of statistics on the EVI of the single farm field.
PROVISIONAL daily 30 meter (m) global HLS Sentinel-2 Multi-spectral Instrument Surface Reflectance - HLSS30.015
PROVISIONAL daily 30 meter (m) global HLS Landsat-8 OLI Surface Reflectance - HLSL30.015
It is recommended to use Conda, an environment manager to set up a compatible Python environment. Download Conda for your OS here: https://www.anaconda.com/download/. Once you have Conda installed, Follow the instructions below to successfully setup a Python environment on Linux, MacOS, or Windows.
This Python Jupyter Notebook tutorial has been tested using Python version 3.7. Conda was used to create the python environment.
Using your preferred command line interface (command prompt, terminal, cmder, etc.) type the following to successfully create a compatible python environment:
conda create -n hlstutorial -c conda-forge --yes python=3.7 gdal rasterio shapely geopandas geoviews holoviews xarray matplotlib cartopy scikit-image hvplot pyepsg
conda activate hlstutorial
conda install jupyter notebook --yes
jupyter notebook
TIP: Having trouble activating your environment, or loading specific packages once you have activated your environment? Try the following:
Type:
conda update condaorconda update --all
If you prefer to not install Conda, the same setup and dependencies can be achieved by using another package manager such as pip.
Setting up a netrc File section in the README.¶The repository containing all of the required files is located at: https://git.earthdata.nasa.gov/projects/LPDUR/repos/hls-tutorial/browse
import os
from datetime import datetime
import requests as r
import numpy as np
import pandas as pd
import geopandas as gp
from skimage import io
import matplotlib.pyplot as plt
from osgeo import gdal
import rasterio as rio
from rasterio.mask import mask
from rasterio.enums import Resampling
from rasterio.shutil import copy
import pyproj
from pyproj import Proj
from shapely.ops import transform
import xarray as xr
import geoviews as gv
from cartopy import crs
import hvplot.xarray
import holoviews as hv
gv.extension('bokeh', 'matplotlib')
# Set Up Working Environment
inDir = os.getcwd()
os.chdir(inDir)
STAC is a specification that provides a common language for interpreting geospatial information in order to standardize indexing and discovering data.
Four STAC Specifications:¶
- STAC API
- STAC Catalog
- STAC Collection
- STAC Item
#### In the section below, we will walk through an example of each specification. For additional information, check out: https://stacspec.org/.
stac = 'https://cmr.earthdata.nasa.gov/stac/' # CMR-STAC API Endpoint
stac_response = r.get(stac).json() # Call the STAC API endpoint
for s in stac_response: print(s)
id title stac_version description links
print(f"You are now using the {stac_response['id']} API (STAC Version: {stac_response['stac_version']}). {stac_response['description']}")
print(f"There are {len(stac_response['links'])} STAC catalogs available in CMR.")
You are now using the stac API (STAC Version: 1.0.0-beta.2). This is the landing page for CMR-STAC. Each provider link below contains a STAC endpoint. There are 46 STAC catalogs available in CMR.
LPCLOUD.¶stac_lp = [s for s in stac_response['links'] if 'LP' in s['title']] # Search for only LP-specific catalogs
# LPCLOUD is the STAC catalog we will be using and exploring today
lp_cloud = r.get([s for s in stac_lp if s['title'] == 'LPCLOUD'][0]['href']).json()
for l in lp_cloud: print(f"{l}: {lp_cloud[l]}")
id: LPCLOUD
title: LPCLOUD
description: Root catalog for LPCLOUD
stac_version: 1.0.0-beta.2
links: [{'rel': 'self', 'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD', 'title': 'Provider catalog', 'type': 'application/json'}, {'rel': 'root', 'href': 'https://cmr.earthdata.nasa.gov/stac/', 'title': 'Root catalog', 'type': 'application/json'}, {'rel': 'collections', 'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections', 'title': 'Provider Collections', 'type': 'application/json'}, {'rel': 'search', 'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD/search', 'title': 'Provider Item Search', 'type': 'application/json'}, {'rel': 'child', 'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections/ASTGTM.v003', 'type': 'application/json'}, {'rel': 'child', 'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections/HLSL30.v1.5', 'type': 'application/json'}, {'rel': 'child', 'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections/HLSS30.v1.5', 'type': 'application/json'}]
lp_links = lp_cloud['links']
for l in lp_links:
try:
print(f"{l['href']} is the {l['title']}")
except:
print(f"{l['href']}")
https://cmr.earthdata.nasa.gov/stac/LPCLOUD is the Provider catalog https://cmr.earthdata.nasa.gov/stac/ is the Root catalog https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections is the Provider Collections https://cmr.earthdata.nasa.gov/stac/LPCLOUD/search is the Provider Item Search https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections/ASTGTM.v003 https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections/HLSL30.v1.5 https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections/HLSS30.v1.5
lp_collections = [l['href'] for l in lp_links if l['rel'] == 'collections'][0] # Set collections endpoint to variable
collections_response = r.get(f"{lp_collections}").json() # Call collections endpoint
print(f"This collection contains {collections_response['description']} ({len(collections_response['collections'])} available)")
This collection contains All collections provided by LPCLOUD (3 available)
collections = collections_response['collections']
collections[1]
{'id': 'HLSL30.v1.5',
'stac_version': '1.0.0-beta.2',
'license': 'not-provided',
'title': 'HLS Operational Land Imager Surface Reflectance and TOA Brightness Daily Global 30 m V1.5',
'description': 'PROVISIONAL - The Harmonized Landsat and Sentinel-2 (HLS) project provides consistent surface reflectance (SR) and top of atmosphere (TOA) brightness data from the Operational Land Imager (OLI) aboard the joint NASA/USGS Landsat 8 satellite and the Multi-Spectral Instrument (MSI) aboard Europe’s Copernicus Sentinel-2A and Sentinel-2B satellites. The combined measurement enables global observations of the land every 2–3 days at 30-meter (m) spatial resolution. The HLS project uses a set of algorithms to obtain seamless products from OLI and MSI that include atmospheric correction, cloud and cloud-shadow masking, spatial co-registration and common gridding, illumination and view angle normalization, and spectral bandpass adjustment. \r\n\r\nThe HLSL30 product provides 30-m Nadir Bidirectional Reflectance Distribution Function (BRDF)-Adjusted Reflectance (NBAR) and is derived from Landsat 8 OLI data products. The HLSS30 (https://doi.org/10.5067/HLS/HLSS30.015) and HLSL30 products are gridded to the same resolution and Military Grid Reference System (MGRS) (https://hls.gsfc.nasa.gov/products-description/tiling-system/) tiling system, and thus are “stackable” for time series analysis.\r\n\r\nThe HLSL30 product is provided in Cloud Optimized GeoTIFF (COG) format, and each band is distributed as a separate file. There are 10 bands included in the HLSL30 product along with one quality assessment (QA) band and four angle bands. For a more detailed description of the individual bands provided in the HLSL30 product, please see the User Guide (https://lpdaac.usgs.gov/documents/878/HLS_User_Guide_V15_provisional.pdf).\r\n\r\n',
'links': [{'rel': 'self',
'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections/HLSL30.v1.5',
'title': 'Info about this collection',
'type': 'application/json'},
{'rel': 'root',
'href': 'https://cmr.earthdata.nasa.gov/stac',
'title': 'Root catalog',
'type': 'application/json'},
{'rel': 'parent',
'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD',
'title': 'Parent catalog',
'type': 'application/json'},
{'rel': 'items',
'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections/HLSL30.v1.5/items',
'title': 'Granules in this collection',
'type': 'application/json'},
{'rel': 'about',
'href': 'https://cmr.earthdata.nasa.gov/search/concepts/C1711972753-LPCLOUD.html',
'title': 'HTML metadata for collection',
'type': 'text/html'},
{'rel': 'via',
'href': 'https://cmr.earthdata.nasa.gov/search/concepts/C1711972753-LPCLOUD.json',
'title': 'CMR JSON metadata for collection',
'type': 'application/json'}],
'extent': {'crs': 'http://www.opengis.net/def/crs/OGC/1.3/CRS84',
'spatial': {'bbox': [[-180, -90, 180, 90]]},
'trs': 'http://www.opengis.net/def/uom/ISO-8601/0/Gregorian',
'temporal': {'interval': [['2013-04-11T00:00:00.000Z', None]]}}}
id is used to query by a specific product, so be sure to save the ID for the HLS S30 and L30 V1.5 products below:¶# Search available collections for HLS and print them out
hls_collections = [c for c in collections if 'HLS' in c['title']]
for h in hls_collections: print(f"{h['title']} has an ID (shortname) of: {h['id']}")
HLS Operational Land Imager Surface Reflectance and TOA Brightness Daily Global 30 m V1.5 has an ID (shortname) of: HLSL30.v1.5 HLS Sentinel-2 Multi-spectral Instrument Surface Reflectance Daily Global 30 m V1.5 has an ID (shortname) of: HLSS30.v1.5
Note that the "id" shortname is in the format: productshortname.vVVV (where VVV = product version)¶
s30 = [h for h in hls_collections if h['id'] == 'HLSS30.v1.5'][0] # Grab HLSS30 collection
for s in s30['extent']: print(f"{s}: {s30['extent'][s]}") # Check out the extent of this collection
crs: http://www.opengis.net/def/crs/OGC/1.3/CRS84
spatial: {'bbox': [[-180, -90, 180, 90]]}
trs: http://www.opengis.net/def/uom/ISO-8601/0/Gregorian
temporal: {'interval': [['2014-04-03T00:00:00.000Z', None]]}
print(f"HLS S30 Start Date is: {s30['extent']['temporal']['interval'][0][0]}")
s30_id = s30['id']
HLS S30 Start Date is: 2014-04-03T00:00:00.000Z
l30 = [h for h in hls_collections if h['id'] == 'HLSL30.v1.5'][0] # Grab HLSL30 collection
for l in l30['extent']: print(f"{l}: {l30['extent'][l]}") # Check out the extent of this collection
print(f"HLS L30 Start Date is: {l30['extent']['temporal']['interval'][0][0]}")
l30_id = l30['id']
crs: http://www.opengis.net/def/crs/OGC/1.3/CRS84
spatial: {'bbox': [[-180, -90, 180, 90]]}
trs: http://www.opengis.net/def/uom/ISO-8601/0/Gregorian
temporal: {'interval': [['2013-04-11T00:00:00.000Z', None]]}
HLS L30 Start Date is: 2013-04-11T00:00:00.000Z
# Below, go through all links in the collection and return the link containing the items endpoint
s30_items = [s['href'] for s in s30['links'] if s['rel'] == 'items'][0] # Set items endpoint to variable
s30_items
'https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections/HLSS30.v1.5/items'
s30_items_response = r.get(f"{s30_items}").json() # Call items endpoint
s30_item = s30_items_response['features'][0] # select first item (10 items returned by default)
s30_item
{'type': 'Feature',
'id': 'G1969487860-LPCLOUD',
'stac_version': '1.0.0-beta.2',
'stac_extensions': ['eo'],
'collection': 'HLSS30.v1.5',
'geometry': {'type': 'Polygon',
'coordinates': [[[-119.1488671, 33.3327671],
[-118.9832795, 33.3355226],
[-118.6783731, 34.3301598],
[-119.1737801, 34.3223655],
[-119.1488671, 33.3327671]]]},
'bbox': [-119.17378, 33.332767, -118.678373, 34.33016],
'links': [{'rel': 'self',
'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections/HLSS30.v1.5/items/G1969487860-LPCLOUD'},
{'rel': 'parent',
'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections/HLSS30.v1.5'},
{'rel': 'collection',
'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections/HLSS30.v1.5'},
{'rel': 'root', 'href': 'https://cmr.earthdata.nasa.gov/stac/'},
{'rel': 'provider', 'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD'},
{'rel': 'via',
'href': 'https://cmr.earthdata.nasa.gov/search/concepts/G1969487860-LPCLOUD.json'},
{'rel': 'via',
'href': 'https://cmr.earthdata.nasa.gov/search/concepts/G1969487860-LPCLOUD.umm_json'}],
'properties': {'datetime': '2015-08-26T18:54:35.450Z',
'start_datetime': '2015-08-26T18:54:35.450Z',
'end_datetime': '2015-08-26T18:54:35.450Z',
'eo:cloud_cover': 6},
'assets': {'VZA': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.VZA.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.VZA.tif'},
'VAA': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.VAA.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.VAA.tif'},
'SAA': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.SAA.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.SAA.tif'},
'B11': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.B11.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.B11.tif'},
'B02': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.B02.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.B02.tif'},
'B09': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.B09.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.B09.tif'},
'B12': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.B12.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.B12.tif'},
'B03': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.B03.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.B03.tif'},
'B01': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.B01.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.B01.tif'},
'B07': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.B07.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.B07.tif'},
'SZA': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.SZA.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.SZA.tif'},
'B05': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.B05.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.B05.tif'},
'B06': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.B06.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.B06.tif'},
'Fmask': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.Fmask.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.Fmask.tif'},
'B10': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.B10.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.B10.tif'},
'B08': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.B08.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.B08.tif'},
'B8A': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.B8A.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.B8A.tif'},
'B04': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.B04.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.B04.tif'},
'browse': {'name': 'Download HLS.S30.T11SLT.2015238T185436.v1.5.jpg',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-public/HLSS30.015/HLS.S30.T11SLT.2015238T185436.v1.5.jpg',
'type': 'image/jpeg'},
'metadata': {'href': 'https://cmr.earthdata.nasa.gov/search/concepts/G1969487860-LPCLOUD.xml',
'type': 'application/xml'}}}
# Print metadata attributes from this observation
print(f"The ID for this item is: {s30_item['id']}")
print(f"It was acquired on: {s30_item['properties']['datetime']}")
print(f"over: {s30_item['bbox']} (Lower Left, Upper Right corner coordinates)")
print(f"It contains {len(s30_item['assets'])} assets")
print(f"and is {s30_item['properties']['eo:cloud_cover']}% cloudy.")
The ID for this item is: G1969487860-LPCLOUD It was acquired on: 2015-08-26T18:54:35.450Z over: [-119.17378, 33.332767, -118.678373, 34.33016] (Lower Left, Upper Right corner coordinates) It contains 20 assets and is 6% cloudy.
for i, s in enumerate(s30_items_response['features']):
print(f"Item at index {i} is {s['properties']['eo:cloud_cover']}% cloudy.")
Item at index 0 is 6% cloudy. Item at index 1 is 100% cloudy. Item at index 2 is 30% cloudy. Item at index 3 is 67% cloudy. Item at index 4 is 99% cloudy. Item at index 5 is 24% cloudy. Item at index 6 is 15% cloudy. Item at index 7 is 3% cloudy. Item at index 8 is 6% cloudy. Item at index 9 is 6% cloudy.
item_index below to whichever observation is the least cloudy above.¶item_index = 7 # Indexing starts at 0 in Python, so here select the eighth item in the list at index 7
s30_item = s30_items_response['features'][item_index] # Grab the next item in the list
print(f"The ID for this item is: {s30_item['id']}")
print(f"It was acquired on: {s30_item['properties']['datetime']}")
print(f"over: {s30_item['bbox']} (Lower Left, Upper Right corner coordinates)")
print(f"It contains {len(s30_item['assets'])} assets")
print(f"and is {s30_item['properties']['eo:cloud_cover']}% cloudy.")
The ID for this item is: G2010295229-LPCLOUD It was acquired on: 2016-11-06T08:21:39.880Z over: [30.227023, -13.64749, 31.059614, -12.649679] (Lower Left, Upper Right corner coordinates) It contains 20 assets and is 3% cloudy.
print("The following assets are available for download:")
for a in s30_item['assets']: print(a)
The following assets are available for download: B04 B05 B01 B08 B09 B06 B8A SAA B07 SZA B10 VZA B03 Fmask B02 B12 B11 VAA browse metadata
s30_item['assets']['browse']
{'name': 'Download HLS.S30.T36LTL.2016311T080122.v1.5.jpg',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-public/HLSS30.015/HLS.S30.T36LTL.2016311T080122.v1.5.jpg',
'type': 'image/jpeg'}
skimage package to load the browse image into memory and matplotlib to quickly visualize it.¶image = io.imread(s30_item['assets']['browse']['href']) # Load jpg browse image into memory
# Basic plot of the image
plt.figure(figsize=(10,10))
plt.imshow(image)
plt.show()
# Remove unnecessary variables
del image, s30_items, s30_items_response, stac_lp, stac_response, l30
del a, collections, collections_response, h, hls_collections, l, lp_cloud, lp_collections, s, s30, s30_item
lp_search = [l['href'] for l in lp_links if l['rel'] == 'search'][0] # Define the search endpoint
# Set up a dictionary that will be used to POST requests to the search endpoint
params = {}
search_response = r.post(lp_search, json=params).json() # Send POST request to retrieve items
print(f"{len(search_response['features'])} items found!")
10 items found!
lim = 100
params['limit'] = lim # Add in a limit parameter to retrieve 100 items at a time.
print(params)
{'limit': 100}
params dictionary.¶search_response = r.post(lp_search, json=params).json() # send POST request to retrieve first 100 items in the STAC collection
print(f"{len(search_response['features'])} items found!")
100 items found!
geopandas. You will need to have downloaded the Field_Boundary.geojson from the repo, and it must be stored in the current working directory in order to continue.¶# Bring in the farm field region of interest
field = gp.read_file('Field_Boundary.geojson')
field
| geometry | |
|---|---|
| 0 | POLYGON ((-122.05172 39.91309, -122.06227 39.9... |
field = gp.read_file('C:/Username/HLS-Tutorial/Field_Boundary.geojson') and try again.¶fieldShape = field['geometry'][0] # Define the geometry as a shapely polygon
fieldShape
geoviews plots using *) with a basemap layer.¶# Use geoviews to combine a basemap with the shapely polygon of our Region of Interest (ROI)
base = gv.tile_sources.EsriImagery.opts(width=500, height=500)
farmField = gv.Polygons(fieldShape).opts(line_color='yellow', color=None)
base * farmField
bbox = f'{fieldShape.bounds[0]},{fieldShape.bounds[1]},{fieldShape.bounds[2]},{fieldShape.bounds[3]}' # Defined from ROI bounds
params['bbox'] = bbox # Add ROI to params
params
{'limit': 100,
'bbox': '-122.0622682571411,39.897234301806,-122.04918980598451,39.91309383703065'}
search_response = r.post(lp_search, json=params).json() # Send POST request with bbox included
print(f"{len(search_response['features'])} items found!")
67 items found!
datetime parameter. Here we have set the time period of interest from September 2020 through March 2021. Additional information on setting temporal searches can be found in the NASA CMR Documentation.¶date_time = "2020-09-01T00:00:00Z/2021-03-31T23:59:59Z" # Define start time period / end time period
params['datetime'] = date_time
params
{'limit': 100,
'bbox': '-122.0622682571411,39.897234301806,-122.04918980598451,39.91309383703065',
'datetime': '2020-09-01T00:00:00Z/2021-03-31T23:59:59Z'}
search_response = r.post(lp_search, json=params).json() # Send POST request with datetime included
print(f"{len(search_response['features'])} items found!")
64 items found!
shortname for the HLSS30 v1.5 product (HLSS30.v1.5) to the params dictionary, and query the CMR-STAC LPCLOUD search endpoint for just HLSS30 items.¶s30_id = "HLSS30.v1.5"
params["collections"] = [s30_id]
# Search for the HLSS30 items of interest:
s30_items = r.post(lp_search, json=params).json()['features'] # Send POST request with collection included
len(s30_items)
58
collections parameter.¶l30_id = "HLSL30.v1.5"
params["collections"].append(l30_id)
params
{'limit': 100,
'bbox': '-122.0622682571411,39.897234301806,-122.04918980598451,39.91309383703065',
'datetime': '2020-09-01T00:00:00Z/2021-03-31T23:59:59Z',
'collections': ['HLSS30.v1.5', 'HLSL30.v1.5']}
collections parameter is a list and can include multiple product collection short names.¶# Search for the HLSS30 and HLSL30 items of interest:
hls_items = r.post(lp_search, json=params).json()['features'] # Send POST request with S30 and L30 collections included
len(hls_items)
64
del bbox, date_time, field, lim, lp_links, lp_search, search_response, s30_items # Remove
# GDAL configurations used to successfully access LP DAAC Cloud Assets via vsicurl
gdal.SetConfigOption('GDAL_HTTP_COOKIEFILE','~/cookies.txt')
gdal.SetConfigOption('GDAL_HTTP_COOKIEJAR', '~/cookies.txt')
gdal.SetConfigOption('GDAL_DISABLE_READDIR_ON_OPEN','YES')
gdal.SetConfigOption('CPL_VSIL_CURL_ALLOWED_EXTENSIONS','TIF')
#gdal.SetConfigOption("GDAL_HTTP_UNSAFESSL", "YES")
h = hls_items[0]
h
{'type': 'Feature',
'id': 'G1948667649-LPCLOUD',
'stac_version': '1.0.0-beta.2',
'stac_extensions': ['eo'],
'collection': 'HLSS30.v1.5',
'geometry': {'type': 'Polygon',
'coordinates': [[[-121.9637022, 39.6569837],
[-121.70654, 40.3901091],
[-121.7016593, 40.6435572],
[-123.0002366, 40.6508565],
[-123.0002332, 39.661607],
[-121.9637022, 39.6569837]]]},
'bbox': [-123.000237, 39.656984, -121.701659, 40.650856],
'links': [{'rel': 'self',
'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections/HLSS30.v1.5/items/G1948667649-LPCLOUD'},
{'rel': 'parent',
'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections/HLSS30.v1.5'},
{'rel': 'collection',
'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD/collections/HLSS30.v1.5'},
{'rel': 'root', 'href': 'https://cmr.earthdata.nasa.gov/stac/'},
{'rel': 'provider', 'href': 'https://cmr.earthdata.nasa.gov/stac/LPCLOUD'},
{'rel': 'via',
'href': 'https://cmr.earthdata.nasa.gov/search/concepts/G1948667649-LPCLOUD.json'},
{'rel': 'via',
'href': 'https://cmr.earthdata.nasa.gov/search/concepts/G1948667649-LPCLOUD.umm_json'}],
'properties': {'datetime': '2020-09-29T19:13:24.996Z',
'start_datetime': '2020-09-29T19:13:24.996Z',
'end_datetime': '2020-09-29T19:13:33.217Z',
'eo:cloud_cover': 21},
'assets': {'B06': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.B06.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B06.tif'},
'VZA': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.VZA.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.VZA.tif'},
'B09': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.B09.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B09.tif'},
'Fmask': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.Fmask.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.Fmask.tif'},
'B01': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.B01.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B01.tif'},
'B12': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.B12.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B12.tif'},
'B11': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.B11.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B11.tif'},
'B8A': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.B8A.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B8A.tif'},
'SAA': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.SAA.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.SAA.tif'},
'B10': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.B10.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B10.tif'},
'B02': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.B02.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B02.tif'},
'B03': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.B03.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B03.tif'},
'B05': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.B05.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B05.tif'},
'B08': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.B08.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B08.tif'},
'VAA': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.VAA.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.VAA.tif'},
'B07': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.B07.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B07.tif'},
'SZA': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.SZA.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.SZA.tif'},
'B04': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.B04.tif',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B04.tif'},
'browse': {'name': 'Download HLS.S30.T10TEK.2020273T190109.v1.5.jpg',
'href': 'https://lpdaac.earthdata.nasa.gov/lp-prod-public/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.jpg',
'type': 'image/jpeg'},
'metadata': {'href': 'https://cmr.earthdata.nasa.gov/search/concepts/G1948667649-LPCLOUD.xml',
'type': 'application/xml'}}}
evi_band_links = []
# Define which HLS product is being accessed
if h['assets']['browse']['href'].split('/')[4] == 'HLSS30.015':
evi_bands = ['B8A', 'B04', 'B02', 'Fmask'] # NIR RED BLUE Quality for S30
else:
evi_bands = ['B05', 'B04', 'B02', 'Fmask'] # NIR RED BLUE Quality for L30
# Subset the assets in the item down to only the desired bands
for a in h['assets']:
if any(b == a for b in evi_bands):
evi_band_links.append(h['assets'][a]['href'])
for e in evi_band_links: print(e)
https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.Fmask.tif https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B8A.tif https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B02.tif https://lpdaac.earthdata.nasa.gov/lp-prod-protected/HLSS30.015/HLS.S30.T10TEK.2020273T190109.v1.5.B04.tif
image = io.imread(h['assets']['browse']['href']) # Load jpg browse image into memory
# Basic plot of the image
plt.figure(figsize=(10,10))
plt.imshow(image)
plt.show()
del image # Remove the browse image
Before loading the COGs into memory, run the cell below to check and make sure that you have a netrc file set up with your NASA Earthdata Login credentials, which will be needed to access the HLS files in the cells that follow. If you do not have a netrc file set up on your OS, the cell below should prompt you for your NASA Earthdata Login username and password.¶
# AUTHENTICATION CONFIGURATION
from netrc import netrc
from subprocess import Popen, DEVNULL, STDOUT
from getpass import getpass
from sys import platform
urs = 'urs.earthdata.nasa.gov' # Earthdata URL to call for authentication
prompts = ['Enter NASA Earthdata Login Username \n(or create an account at urs.earthdata.nasa.gov): ',
'Enter NASA Earthdata Login Password: ']
# Determine if netrc file exists, and if it includes NASA Earthdata Login Credentials
if 'win' in platform:
nrc = '_netrc'
else:
nrc = '.netrc'
try:
netrcDir = os.path.expanduser(f"~/{nrc}")
netrc(netrcDir).authenticators(urs)[0]
del netrcDir
# If not, create a netrc file and prompt user for NASA Earthdata Login Username/Password
except FileNotFoundError:
homeDir = os.path.expanduser("~")
# Windows OS won't read the netrc unless this is set
Popen(f'setx HOME {homeDir}', shell=True, stdout=DEVNULL);
if nrc == '.netrc':
Popen(f'touch {homeDir + os.sep}{nrc} | chmod og-rw {homeDir + os.sep}{nrc}', shell=True, stdout=DEVNULL, stderr=STDOUT);
# Unable to use touch/chmod on Windows OS
Popen(f'echo machine {urs} >> {homeDir + os.sep}{nrc}', shell=True)
Popen(f'echo login {getpass(prompt=prompts[0])} >> {homeDir + os.sep}{nrc}', shell=True)
Popen(f'echo password {getpass(prompt=prompts[1])} >> {homeDir + os.sep}{nrc}', shell=True)
del homeDir
# Determine OS and edit netrc file if it exists but is not set up for NASA Earthdata Login
except TypeError:
homeDir = os.path.expanduser("~")
Popen(f'echo machine {urs} >> {homeDir + os.sep}{nrc}', shell=True)
Popen(f'echo login {getpass(prompt=prompts[0])} >> {homeDir + os.sep}{nrc}', shell=True)
Popen(f'echo password {getpass(prompt=prompts[1])} >> {homeDir + os.sep}{nrc}', shell=True)
del homeDir
del urs, prompts
rasterio.¶# Use vsicurl to load the data directly into memory (be patient, may take a few seconds)
for e in evi_band_links:
if e.rsplit('.', 2)[-2] == evi_bands[0]: # NIR index
nir = rio.open(e)
elif e.rsplit('.', 2)[-2] == evi_bands[1]: # red index
red = rio.open(e)
elif e.rsplit('.', 2)[-2] == evi_bands[2]: # blue index
blue = rio.open(e)
elif e.rsplit('.', 2)[-2] == evi_bands[3]: # Fmask index
fmask = rio.open(e)
print("The COGs have been loaded into memory!")
The COGs have been loaded into memory!
Setting up a netrc File section in the README.¶shapely polygon and convert it from lat/lon (EPSG: 4326) into the native projection of HLS, UTM (aligned to the Military Grid Reference System). This must be done in order to use the Region of Interest (ROI) to subset the COG that is being pulled into memory--it must be in the native projection of the data being extracted.¶geo_CRS = Proj('+proj=longlat +datum=WGS84 +no_defs', preserve_units=True) # Source coordinate system of the ROI
utm = pyproj.Proj(nir.crs) # Destination coordinate system
project = pyproj.Transformer.from_proj(geo_CRS, utm) # Set up the transformation
fsUTM = transform(project.transform, fieldShape) # Apply reprojection
rasterio. This greatly reduces the amount of data that are needed to load into memory.¶nir_array, nir_transform = rio.mask.mask(nir, [fsUTM], crop=True) # Extract the data for the ROI and clip to that bbox
plt.imshow(nir_array[0]); # Quick visual to assure that it worked
red_array, _ = rio.mask.mask(red,[fsUTM],crop=True)
blue_array, _ = rio.mask.mask(blue,[fsUTM],crop=True)
print('Data is loaded into memory!')
Data is loaded into memory!
del a, e, evi_band_links, evi_bands # Remove variables that are no longer needed
nir.meta
{'driver': 'GTiff',
'dtype': 'int16',
'nodata': -9999.0,
'width': 3660,
'height': 3660,
'count': 1,
'crs': CRS.from_wkt('PROJCS["UTM Zone 10, Northern Hemisphere",GEOGCS["Unknown datum based upon the WGS 84 ellipsoid",DATUM["Not_specified_based_on_WGS_84_spheroid",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",-123],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH]]'),
'transform': Affine(30.0, 0.0, 499980.0,
0.0, -30.0, 4500000.0)}
nan.¶# Grab scale factor from metadata and apply to each band
nir_scaled = nir_array[0] * nir.scales[0]
red_scaled = red_array[0] * red.scales[0]
blue_scaled = blue_array[0] * blue.scales[0]
# Set all nodata values to nan
nir_scaled[nir_array[0]==nir.nodata] = np.nan
red_scaled[red_array[0]==red.nodata] = np.nan
blue_scaled[blue_array[0]==blue.nodata] = np.nan
def evi(red, blue, nir):
return 2.5 * (nir - red) / (nir + 6.0 * red - 7.5 * blue + 1.0)
evi_scaled = evi(red_scaled, blue_scaled, nir_scaled) # Generate EVI array
eviDate = h['properties']['datetime'].split('T')[0] # Set the observation date to a variable
matplotlib.¶# Ignore matplotlib warnings
import warnings
warnings.filterwarnings('ignore')
fig = plt.figure(figsize = (10,7.5)) # Set the figure size (x,y)
plt.axis('off') # Remove the axes' values
fig.suptitle('HLS-derived EVI Data', fontsize=14, fontweight='bold') # Make a figure title
ax = fig.add_subplot(111) # Make a subplot
ax.set_title(f'Tehama County, CA: {eviDate}', fontsize=12, fontweight='bold') # Add figure subtitle
ax1 = plt.gca() # Get current axes
# Plot the array, using a colormap and setting a custom linear stretch
im = plt.imshow(evi_scaled, vmin=0, vmax=1, cmap='YlGn');
# Add a colormap legend
plt.colorbar(im, orientation='horizontal', fraction=0.047, pad=0.009, label='EVI', shrink=0.6).outline.set_visible(True)
fmask_array, _ = rio.mask.mask(fmask, [fsUTM], crop=True) # Load in the Quality data
bitword_order = (1, 1, 1, 1, 1, 1, 2) # set the number of bits per bitword
num_bitwords = len(bitword_order) # Define the number of bitwords based on your input above
total_bits = sum(bitword_order) # Should be 8, 16, or 32 depending on datatype
- Cloud == 0 (No Cloud)
- Cloud shadow == 0 (No Cloud shadow)
- Snow/ice == 0 (No Snow/ice present)
- Water == 0 (No Water present)
qVals = list(np.unique(fmask_array)) # Create a list of unique values that need to be converted to binary and decoded
all_bits = list()
goodQuality = []
for v in qVals:
all_bits = []
bits = total_bits
i = 0
# Convert to binary based on the values and # of bits defined above:
bit_val = format(v, 'b').zfill(bits)
print('\n' + str(v) + ' = ' + str(bit_val))
all_bits.append(str(v) + ' = ' + str(bit_val))
# Go through & split out the values for each bit word based on input above:
for b in bitword_order:
prev_bit = bits
bits = bits - b
i = i + 1
if i == 1:
bitword = bit_val[bits:]
print(' Bit Word ' + str(i) + ': ' + str(bitword))
all_bits.append(' Bit Word ' + str(i) + ': ' + str(bitword))
elif i == num_bitwords:
bitword = bit_val[:prev_bit]
print(' Bit Word ' + str(i) + ': ' + str(bitword))
all_bits.append(' Bit Word ' + str(i) + ': ' + str(bitword))
else:
bitword = bit_val[bits:prev_bit]
print(' Bit Word ' + str(i) + ': ' + str(bitword))
all_bits.append(' Bit Word ' + str(i) + ': ' + str(bitword))
# 2, 4, 5, 6 are the bits used. All 4 should = 0 if no clouds, cloud shadows were present, and pixel is not snow/ice/water
if int(all_bits[2].split(': ')[-1]) + int(all_bits[4].split(': ')[-1]) + \
int(all_bits[5].split(': ')[-1]) + int(all_bits[6].split(': ')[-1]) == 0:
goodQuality.append(v)
0 = 00000000 Bit Word 1: 0 Bit Word 2: 0 Bit Word 3: 0 Bit Word 4: 0 Bit Word 5: 0 Bit Word 6: 0 Bit Word 7: 00 16 = 00010000 Bit Word 1: 0 Bit Word 2: 0 Bit Word 3: 0 Bit Word 4: 0 Bit Word 5: 1 Bit Word 6: 0 Bit Word 7: 00 255 = 11111111 Bit Word 1: 1 Bit Word 2: 1 Bit Word 3: 1 Bit Word 4: 1 Bit Word 5: 1 Bit Word 6: 1 Bit Word 7: 11
goodQuality
[0]
evi_band = np.ma.MaskedArray(evi_scaled, np.in1d(fmask_array, goodQuality, invert=True)) # Apply QA mask to the EVI data
evi_band = np.ma.filled(evi_band, np.nan) # Set masked data to nan
originalName = nir.name.rsplit('/', 1)[-1] # Grab the original granule name
originalName
'HLS.S30.T10TEK.2020273T190109.v1.5.B8A.tif'
HLS.S30/HLS.L30: Product Short Name
T10TEK: MGRS Tile ID (T+5-digits)
2020273T190109: Julian Date and Time of Acquisition (YYYYDDDTHHMMSS)
v1.5: Product Version
B8A/B05: Spectral Band
.tif: Data Format (Cloud Optimized GeoTIFF)For additional information on HLS naming conventions, be sure to check out the HLS Overview Page.¶
outName = f"{originalName.split('.B')[0]}_EVI.tif" # Generate output name from the original filename
tempName = 'temp.tif' # Set up temp file
outName
'HLS.S30.T10TEK.2020273T190109.v1.5_EVI.tif'
# Create output GeoTIFF with overviews
evi_tif = rio.open(tempName, 'w', driver='GTiff', height=evi_band.shape[0], width=evi_band.shape[1], count=1,
dtype=str(evi_band.dtype), crs=nir.crs, transform=nir_transform)
evi_tif.write(evi_band, 1) # Write the EVI band to the newly created GeoTIFF
evi_tif.build_overviews([2, 4, 8], Resampling.average) # Calculate overviews
evi_tif.update_tags(ns='rio_overview', resampling='average') # Update tags
# Copy the profile, add tiling and compression
kwds = evi_tif.profile
kwds['tiled'] = True
kwds['compress'] = 'LZW'
evi_tif.overviews(1) # Print overviews
[2, 4, 8]
evi_tif.close() # Close file
# Open temp file and export as valid cog
with rio.open(tempName, 'r+') as src:
copy(src, outName, copy_src_overviews=True, **kwds)
src.close(), os.remove(tempName);
src.close(), nir.close(), red.close(), fmask.close(), blue.close()
del nir_array, red_array, blue_array, red_scaled, blue_scaled, nir_scaled, i, originalName, outName, prev_bit, qVals, v
del all_bits, b, bit_val, bits, bitword, eviDate, evi_band, evi_scaled, fmask_array, goodQuality, h
len(hls_items)
64
# Now put it all together and loop through each of the files, visualize, calculate statistics on EVI, and export
for j, h in enumerate(hls_items):
outName = h['assets']['browse']['href'].split('/')[-1].replace('.jpg', '_EVI.tif')
# Check if file already exists in output directory, if yes--skip that file and move to the next observation
if os.path.exists(outName):
print(f"{outName} has already been processed and is available in this directory, moving to next file.")
continue
try:
evi_band_links = []
if h['assets']['browse']['href'].split('/')[4] == 'HLSS30.015':
evi_bands = ['B8A', 'B04', 'B02', 'Fmask'] # NIR RED BLUE FMASK
else:
evi_bands = ['B05', 'B04', 'B02', 'Fmask'] # NIR RED BLUE FMASK
for a in h['assets']:
if any(b == a for b in evi_bands):
evi_band_links.append(h['assets'][a]['href'])
# Use vsicurl to load the data directly into memory (be patient, may take a few seconds)
for e in evi_band_links:
if e.rsplit('.', 2)[-2] == evi_bands[0]: # NIR index
nir = rio.open(e)
elif e.rsplit('.', 2)[-2] == evi_bands[1]: # red index
red = rio.open(e)
elif e.rsplit('.', 2)[-2] == evi_bands[2]: # blue index
blue = rio.open(e)
elif e.rsplit('.', 2)[-2] == evi_bands[3]: # fmask index
fmask = rio.open(e)
# load data and scale
nir_array,nir_transform = rio.mask.mask(nir,[fsUTM],crop=True)
red_array, _ = rio.mask.mask(red,[fsUTM],crop=True)
blue_array, _ = rio.mask.mask(blue,[fsUTM],crop=True)
nir_scaled = nir_array[0] * nir.scales[0]
red_scaled = red_array[0] * red.scales[0]
blue_scaled = blue_array[0] * blue.scales[0]
nir_scaled[nir_array[0]==nir.nodata] = np.nan
red_scaled[red_array[0]==red.nodata] = np.nan
blue_scaled[blue_array[0]==blue.nodata] = np.nan
# Generate EVI
evi_scaled = evi(red_scaled, blue_scaled, nir_scaled)
# Quality Filter the data
fmask_array, _ = rio.mask.mask(fmask,[fsUTM],crop=True)
qVals = list(np.unique(fmask_array))
all_bits = list()
goodQuality = []
for v in qVals:
all_bits = []
bits = total_bits
i = 0
# Convert to binary based on the values and # of bits defined above:
bit_val = format(v, 'b').zfill(bits)
all_bits.append(str(v) + ' = ' + str(bit_val))
# Go through & split out the values for each bit word based on input above:
for b in bitword_order:
prev_bit = bits
bits = bits - b
i = i + 1
if i == 1:
bitword = bit_val[bits:]
all_bits.append(' Bit Word ' + str(i) + ': ' + str(bitword))
elif i == num_bitwords:
bitword = bit_val[:prev_bit]
all_bits.append(' Bit Word ' + str(i) + ': ' + str(bitword))
else:
bitword = bit_val[bits:prev_bit]
all_bits.append(' Bit Word ' + str(i) + ': ' + str(bitword))
# 2, 4, 5, 6 are the bits used. All should = 0 if no clouds, cloud shadows were present & pixel is not snow/ice/water
if int(all_bits[2].split(': ')[-1]) + int(all_bits[4].split(': ')[-1]) + \
int(all_bits[5].split(': ')[-1]) + int(all_bits[6].split(': ')[-1]) == 0:
goodQuality.append(v)
evi_band = np.ma.MaskedArray(evi_scaled, np.in1d(fmask_array, goodQuality, invert=True)) # Apply QA mask to the EVI data
evi_band = np.ma.filled(evi_band, np.nan)
# Remove any observations that are entirely fill value
if np.nansum(evi_band) == 0.0:
print(f"File: {h['assets']['browse']['href'].split('/')[-1].rsplit('.', 1)[0]} ({h['id']}) was entirely fill values and will not be exported.")
continue
tempName = "temp.tif"
# Create output GeoTIFF with overviews
evi_tif = rio.open(tempName, 'w', driver='GTiff', height=evi_band.shape[0], width=evi_band.shape[1], count=1,
dtype=str(evi_band.dtype), crs=nir.crs, transform=nir_transform)
evi_tif.write(evi_band, 1)
evi_tif.build_overviews([2, 4, 8], Resampling.average)
evi_tif.update_tags(ns='rio_overview', resampling='average')
# Copy the profile, add tiling and compression
kwds = evi_tif.profile
kwds['tiled'] = True
kwds['compress'] = 'LZW'
evi_tif.close()
# Open temp file and export as valid cog
with rio.open(tempName, 'r+') as src:
copy(src, outName, copy_src_overviews=True, **kwds)
src.close(), os.remove(tempName);
except:
print(f"Unable to access file: {h['assets']['browse']['href'].split('/')[-1].rsplit('.', 1)[0]} ({h['id']})")
print(f"Processing file {j+1} of {len(hls_items)}")
HLS.S30.T10TEK.2020273T190109.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020275T185221.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020278T190241.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020280T185249.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020283T190319.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020285T185321.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020288T190351.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020290T185359.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020295T185431.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020298T190451.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020300T185459.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020303T190519.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020305T185531.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020308T190551.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020310T185559.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020313T190619.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020315T185621.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020320T185649.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. File: HLS.S30.T10TEK.2020323T190659.v1.5 (G1969169065-LPCLOUD) was entirely fill values and will not be exported. HLS.S30.T10TEK.2020325T185711.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020328T190721.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020330T185719.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020333T190739.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020335T185741.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020338T190751.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020340T185749.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020343T190759.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2020345T185801.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. File: HLS.S30.T10TEK.2020348T190811.v1.5 (G1979717320-LPCLOUD) was entirely fill values and will not be exported. HLS.S30.T10TEK.2020353T190809.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. File: HLS.S30.T10TEK.2020355T185811.v1.5 (G1986403285-LPCLOUD) was entirely fill values and will not be exported. HLS.S30.T10TEK.2020358T190811.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. File: HLS.S30.T10TEK.2020363T190809.v1.5 (G1988939025-LPCLOUD) was entirely fill values and will not be exported. HLS.S30.T10TEK.2020365T185811.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. File: HLS.S30.T10TEK.2021002T190811.v1.5 (G1990187354-LPCLOUD) was entirely fill values and will not be exported. File: HLS.S30.T10TEK.2021004T185759.v1.5 (G1990673295-LPCLOUD) was entirely fill values and will not be exported. HLS.S30.T10TEK.2021009T185751.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2021014T185729.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2021017T190729.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2021019T185711.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. File: HLS.S30.T10TEK.2021022T190711.v1.5 (G1996602223-LPCLOUD) was entirely fill values and will not be exported. HLS.L30.T10TEK.2021023T185136.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. File: HLS.S30.T10TEK.2021027T190639.v1.5 (G1999126184-LPCLOUD) was entirely fill values and will not be exported. File: HLS.S30.T10TEK.2021029T185631.v1.5 (G2000270401-LPCLOUD) was entirely fill values and will not be exported. File: HLS.S30.T10TEK.2021032T190621.v1.5 (G2001130099-LPCLOUD) was entirely fill values and will not be exported. HLS.S30.T10TEK.2021037T190549.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. File: HLS.L30.T10TEK.2021039T185134.v1.5 (G2004307161-LPCLOUD) was entirely fill values and will not be exported. HLS.S30.T10TEK.2021044T185459.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2021047T190439.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.L30.T10TEK.2021048T184520.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2021052T190411.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2021054T185349.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.L30.T10TEK.2021055T185128.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2021057T190339.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2021059T185321.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. File: HLS.S30.T10TEK.2021062T190301.v1.5 (G2017260604-LPCLOUD) was entirely fill values and will not be exported. HLS.L30.T10TEK.2021064T184513.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.S30.T10TEK.2021064T185239.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. File: HLS.S30.T10TEK.2021067T190229.v1.5 (G2019631070-LPCLOUD) was entirely fill values and will not be exported. HLS.S30.T10TEK.2021072T190151.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. File: HLS.S30.T10TEK.2021074T185039.v1.5 (G2022997385-LPCLOUD) was entirely fill values and will not be exported. HLS.S30.T10TEK.2021079T185051.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. HLS.L30.T10TEK.2021080T184505.v1.5_EVI.tif has already been processed and is available in this directory, moving to next file. Processing file 64 of 64
# Remove variables that are no longer needed and close the files that were read in memory
del all_bits, b, bit_val, bits, bitword, bitword_order, blue_array, blue_scaled, e, evi_band, evi_band_links, evi_bands
del evi_scaled, fmask_array, goodQuality, h, hls_items, i, nir_array, nir_scaled, nir_transform, num_bitwords, outName
del prev_bit, qVals, red_array, red_scaled, stac, total_bits, v
nir.close(), red.close(), fmask.close(), blue.close();
Xarrayextends and combines much of the core functionality from both the Pandas library and Numpy, hence making it very good at handling multi-dimensional (N-dimensional) datasets that contain labels (e.g., variable names or dimension names).List the files in the current working directory.¶
eviFiles = [o for o in os.listdir() if o.endswith('EVI.tif')] # List EVI COGs
eviFiles
['HLS.L30.T10TEK.2021023T185136.v1.5_EVI.tif', 'HLS.L30.T10TEK.2021048T184520.v1.5_EVI.tif', 'HLS.L30.T10TEK.2021055T185128.v1.5_EVI.tif', 'HLS.L30.T10TEK.2021064T184513.v1.5_EVI.tif', 'HLS.L30.T10TEK.2021080T184505.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020273T190109.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020275T185221.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020278T190241.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020280T185249.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020283T190319.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020285T185321.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020288T190351.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020290T185359.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020295T185431.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020298T190451.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020300T185459.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020303T190519.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020305T185531.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020308T190551.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020310T185559.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020313T190619.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020315T185621.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020320T185649.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020325T185711.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020328T190721.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020330T185719.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020333T190739.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020335T185741.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020338T190751.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020340T185749.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020343T190759.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020345T185801.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020353T190809.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020358T190811.v1.5_EVI.tif', 'HLS.S30.T10TEK.2020365T185811.v1.5_EVI.tif', 'HLS.S30.T10TEK.2021009T185751.v1.5_EVI.tif', 'HLS.S30.T10TEK.2021014T185729.v1.5_EVI.tif', 'HLS.S30.T10TEK.2021017T190729.v1.5_EVI.tif', 'HLS.S30.T10TEK.2021019T185711.v1.5_EVI.tif', 'HLS.S30.T10TEK.2021037T190549.v1.5_EVI.tif', 'HLS.S30.T10TEK.2021044T185459.v1.5_EVI.tif', 'HLS.S30.T10TEK.2021047T190439.v1.5_EVI.tif', 'HLS.S30.T10TEK.2021052T190411.v1.5_EVI.tif', 'HLS.S30.T10TEK.2021054T185349.v1.5_EVI.tif', 'HLS.S30.T10TEK.2021057T190339.v1.5_EVI.tif', 'HLS.S30.T10TEK.2021059T185321.v1.5_EVI.tif', 'HLS.S30.T10TEK.2021064T185239.v1.5_EVI.tif', 'HLS.S30.T10TEK.2021072T190151.v1.5_EVI.tif', 'HLS.S30.T10TEK.2021079T185051.v1.5_EVI.tif', 'HLS.S30.T10TEK.2021082T190041.v1.5_EVI.tif']
for i, e in enumerate(eviFiles):
time = datetime.strptime(e.rsplit('.v1.5', 1)[0].rsplit('.', 1)[-1], '%Y%jT%H%M%S') # Grab acquisition time from filename
# Need to set up the xarray data array for the first file
if i == 0:
eviStack = xr.open_rasterio(e) # Open file using rasterio
eviStack = eviStack.squeeze(drop=True)
eviStack.coords['time'] = np.array(time) # Define time coordinate
eviStack = eviStack.rename({'x':'lon', 'y':'lat', 'time':'time'}) # Rename coordinates
eviStack = eviStack.expand_dims(dim='time')
else:
eviS = xr.open_rasterio(e)
eviS = eviS.squeeze(drop=True)
eviS.coords['time'] = np.array(time)
eviS = eviS.rename({'x':'lon', 'y':'lat', 'time':'time'})
eviS = eviS.expand_dims(dim='time')
eviStack = xr.concat([eviStack, eviS], dim='time') # concatenate the new array to the data array
eviStack.name = 'EVI'
Xarray has two fundamental data structures. A
Datasetholds multiple variables that potentially share the same coordinates and global metadata for the file (see above). ADataArraycontains a single multi-dimensional variable and its coordinates, attributes, and metadata. Data values can be pulled out of the DataArray as anumpy.ndarrayusing thevaluesattribute.
eviStack
<xarray.DataArray 'EVI' (time: 50, lat: 59, lon: 38)>
array([[[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
...,
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan]],
[[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
...,
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan]],
[[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
...,
...
...,
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan]],
[[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
...,
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan]],
[[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
...,
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan]]])
Coordinates:
* lat (lat) float64 4.419e+06 4.419e+06 4.418e+06 ... 4.417e+06 4.417e+06
* lon (lon) float64 5.802e+05 5.802e+05 5.802e+05 ... 5.812e+05 5.813e+05
* time (time) datetime64[ns] 2021-01-23T18:51:36 ... 2021-03-23T19:00:41
Attributes:
transform: (30.0, 0.0, 580140.0, 0.0, -30.0, 4418550.0)
crs: +proj=utm +zone=10 +ellps=WGS84 +units=m +no_defs=True
res: (30.0, 30.0)
is_tiled: 1
nodatavals: (nan,)
scales: (1.0,)
offsets: (0.0,)
AREA_OR_POINT: Areaarray([[[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
...,
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan]],
[[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
...,
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan]],
[[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
...,
...
...,
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan]],
[[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
...,
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan]],
[[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
...,
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan]]])array([4418535., 4418505., 4418475., 4418445., 4418415., 4418385., 4418355.,
4418325., 4418295., 4418265., 4418235., 4418205., 4418175., 4418145.,
4418115., 4418085., 4418055., 4418025., 4417995., 4417965., 4417935.,
4417905., 4417875., 4417845., 4417815., 4417785., 4417755., 4417725.,
4417695., 4417665., 4417635., 4417605., 4417575., 4417545., 4417515.,
4417485., 4417455., 4417425., 4417395., 4417365., 4417335., 4417305.,
4417275., 4417245., 4417215., 4417185., 4417155., 4417125., 4417095.,
4417065., 4417035., 4417005., 4416975., 4416945., 4416915., 4416885.,
4416855., 4416825., 4416795.])array([580155., 580185., 580215., 580245., 580275., 580305., 580335., 580365.,
580395., 580425., 580455., 580485., 580515., 580545., 580575., 580605.,
580635., 580665., 580695., 580725., 580755., 580785., 580815., 580845.,
580875., 580905., 580935., 580965., 580995., 581025., 581055., 581085.,
581115., 581145., 581175., 581205., 581235., 581265.])array(['2021-01-23T18:51:36.000000000', '2021-02-17T18:45:20.000000000',
'2021-02-24T18:51:28.000000000', '2021-03-05T18:45:13.000000000',
'2021-03-21T18:45:05.000000000', '2020-09-29T19:01:09.000000000',
'2020-10-01T18:52:21.000000000', '2020-10-04T19:02:41.000000000',
'2020-10-06T18:52:49.000000000', '2020-10-09T19:03:19.000000000',
'2020-10-11T18:53:21.000000000', '2020-10-14T19:03:51.000000000',
'2020-10-16T18:53:59.000000000', '2020-10-21T18:54:31.000000000',
'2020-10-24T19:04:51.000000000', '2020-10-26T18:54:59.000000000',
'2020-10-29T19:05:19.000000000', '2020-10-31T18:55:31.000000000',
'2020-11-03T19:05:51.000000000', '2020-11-05T18:55:59.000000000',
'2020-11-08T19:06:19.000000000', '2020-11-10T18:56:21.000000000',
'2020-11-15T18:56:49.000000000', '2020-11-20T18:57:11.000000000',
'2020-11-23T19:07:21.000000000', '2020-11-25T18:57:19.000000000',
'2020-11-28T19:07:39.000000000', '2020-11-30T18:57:41.000000000',
'2020-12-03T19:07:51.000000000', '2020-12-05T18:57:49.000000000',
'2020-12-08T19:07:59.000000000', '2020-12-10T18:58:01.000000000',
'2020-12-18T19:08:09.000000000', '2020-12-23T19:08:11.000000000',
'2020-12-30T18:58:11.000000000', '2021-01-09T18:57:51.000000000',
'2021-01-14T18:57:29.000000000', '2021-01-17T19:07:29.000000000',
'2021-01-19T18:57:11.000000000', '2021-02-06T19:05:49.000000000',
'2021-02-13T18:54:59.000000000', '2021-02-16T19:04:39.000000000',
'2021-02-21T19:04:11.000000000', '2021-02-23T18:53:49.000000000',
'2021-02-26T19:03:39.000000000', '2021-02-28T18:53:21.000000000',
'2021-03-05T18:52:39.000000000', '2021-03-13T19:01:51.000000000',
'2021-03-20T18:50:51.000000000', '2021-03-23T19:00:41.000000000'],
dtype='datetime64[ns]')EVI that has lat (y) and lon (x) coordinates, as well as the z dimension, which is time. This allows us to plot and visualize the HLS-derived EVI data as a time series sequentially by time and in geographic space using the lat/lon coordinates.¶.sortby() function to assure that the data for both L30 and S30 are arranged by time.¶eviStack = eviStack.sortby(eviStack.time)
# set the x, y, and z (groupby) dimensions, add a colormap/bar and other parameters.
title = 'HLS-derived EVI over an agricultural field in northern California'
eviStack.hvplot(x='lon', y='lat',groupby='time', cmap='YlGn', width=600, height=600, colorbar=True).opts(clim=(0.0, 1.0),
title=title)
ecrs = pyproj.CRS.to_epsg(pyproj.CRS.from_proj4(eviStack.crs)) # Define CRS
# Use holoviews to create a plot grouped by time and with a specific stretch and colorbar/map applied and shown
hls_stack = eviStack.hvplot(x='lon', y='lat', crs=ecrs, groupby='time', cmap='YlGn', colorbar=True).opts(clim=(0.0, 1.0),
title=title)
base * hls_stack # Add in the basemap created earlier
sTime = '2020-09-29T19:01:09' # Single date
eviStack.sel(time=sTime) # Select a single time slice and pull out of xarray data array
<xarray.DataArray 'EVI' (lat: 59, lon: 38)>
array([[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
...,
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan]])
Coordinates:
* lat (lat) float64 4.419e+06 4.419e+06 4.418e+06 ... 4.417e+06 4.417e+06
* lon (lon) float64 5.802e+05 5.802e+05 5.802e+05 ... 5.812e+05 5.813e+05
time datetime64[ns] 2020-09-29T19:01:09
Attributes:
transform: (30.0, 0.0, 580140.0, 0.0, -30.0, 4418550.0)
crs: +proj=utm +zone=10 +ellps=WGS84 +units=m +no_defs=True
res: (30.0, 30.0)
is_tiled: 1
nodatavals: (nan,)
scales: (1.0,)
offsets: (0.0,)
AREA_OR_POINT: Areaarray([[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
...,
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan],
[nan, nan, nan, ..., nan, nan, nan]])array([4418535., 4418505., 4418475., 4418445., 4418415., 4418385., 4418355.,
4418325., 4418295., 4418265., 4418235., 4418205., 4418175., 4418145.,
4418115., 4418085., 4418055., 4418025., 4417995., 4417965., 4417935.,
4417905., 4417875., 4417845., 4417815., 4417785., 4417755., 4417725.,
4417695., 4417665., 4417635., 4417605., 4417575., 4417545., 4417515.,
4417485., 4417455., 4417425., 4417395., 4417365., 4417335., 4417305.,
4417275., 4417245., 4417215., 4417185., 4417155., 4417125., 4417095.,
4417065., 4417035., 4417005., 4416975., 4416945., 4416915., 4416885.,
4416855., 4416825., 4416795.])array([580155., 580185., 580215., 580245., 580275., 580305., 580335., 580365.,
580395., 580425., 580455., 580485., 580515., 580545., 580575., 580605.,
580635., 580665., 580695., 580725., 580755., 580785., 580815., 580845.,
580875., 580905., 580935., 580965., 580995., 581025., 581055., 581085.,
581115., 581145., 581175., 581205., 581235., 581265.])array('2020-09-29T19:01:09.000000000', dtype='datetime64[ns]')eviStack.sel(time=sTime).hvplot.box(by=['time'], rot=45, box_fill_color='lightblue', padding=0.1, width=450, height=350)
# Select all observations from 2020 and plot each as a boxplot showing the distribution of EVI values for this field
eviStack.hvplot.box('EVI', by=['time'], rot=45, box_fill_color='lightblue', padding=0.1, width=800, height=450)
# xarray allows you to easily calculate a number of statistics
evi_min = eviStack.min(('lat', 'lon'))
evi_max = eviStack.max(('lat', 'lon'))
evi_mean = eviStack.mean(('lat', 'lon'))
evi_sd = eviStack.std(('lat', 'lon'))
evi_count = eviStack.count(('lat', 'lon'))
evi_median = eviStack.median(('lat', 'lon'))
We now have the
meanandstandard deviationfor each time slice as well as themaximumandminimumvalues. Let's do some plotting! We will use thehvPlotpackage to create simple but interactive charts/plots. Hover your curser over the visualization to see the data values.
evi_mean.hvplot.line()
# Ignore warnings
import warnings
warnings.filterwarnings('ignore')
# Combine line plots for different statistics
stats = (evi_mean.hvplot.line(height=350, width=450, line_width=1.5, color='red', grid=True, padding=0.05).opts(title='Mean')+
evi_sd.hvplot.line(height=350, width=450, line_width=1.5, color='red', grid=True, padding=0.05).opts(title='Standard Deviation')
+ evi_max.hvplot.line(height=350, width=450, line_width=1.5, color='red', grid=True, padding=0.05).opts(title='Max') +
evi_min.hvplot.line(height=350, width=450, line_width=1.5, color='red', grid=True, padding=0.05).opts(title='Min')).cols(2)
stats
pandas dataframe with the statistics, and export to a CSV file.¶# Create pandas dataframe from dictionary
df = pd.DataFrame({'Min EVI': evi_min, 'Max EVI': evi_max,
'Mean EVI': evi_mean, 'Standard Deviation EVI': evi_sd,
'Median EVI': evi_median, 'Count': evi_count})
df.index = eviStack.time.data # Set the observation date as the index
df.to_csv('HLS-Derived_EVI_Stats.csv', index=True) # Export to CSV
del eviStack